package com.bhh.nio.filecopy;

import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 * @author bhh
 * @description 文件拷贝性能测试
 *
 * 不使用缓冲区的流和使用缓冲区的流, 运行效率直接相差十分大, Buffer的存在可以使效率大幅提升
 * 使用缓冲区的流 和 使用Buffer的Channel 和 Channel之间直接传输 运行效率相差不大
 * 使用Buffer的Channel并不一定就比 使用缓冲区的流的效率低
 *
 * @date Created in 2021-04-07 21:11
 * @modified By
 */

interface FileCopy {
    void copyFile(File source, File target);
}

@Slf4j
public class FileCopyExam {

    private static Long copyTest(FileCopy fileCopy, File resource, File target) {
        Long start = System.currentTimeMillis();
        for (int i = 0; i < 5; i++) {
            fileCopy.copyFile(resource, target);
        }
        Long end = System.currentTimeMillis();
        return (end - start) / 5;
    }

    // 无论是channel还是stream 都会继承 Closeable接口
    private static void close(Closeable closeable) {
        try {
            closeable.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {

        // 不使用缓冲区的流来完成拷贝
        FileCopy noBufferStreamCopy = new FileCopy() {
            @Override
            public void copyFile(File source, File target) {

                FileInputStream fileInputStream = null;
                FileOutputStream fileOutputStream = null;

                try {
                    fileInputStream = new FileInputStream(source);
                    fileOutputStream = new FileOutputStream(target);

                    // 进行文件拷贝, read()方法返回值为 -1时, 为读完文件
                    int result;
                    while ((result = fileInputStream.read()) != -1) {
                        log.info(String.valueOf(result));
                        fileOutputStream.write(result);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    // 关闭流
                    close(fileInputStream);
                    close(fileOutputStream);
                }
            }
        };

        // 使用缓冲区的流来完成拷贝
        FileCopy bufferStreamCopy = new FileCopy() {
            @Override
            public void copyFile(File source, File target) {
                BufferedInputStream fin = null;
                BufferedOutputStream fout = null;

                try {
                    fin = new BufferedInputStream(new FileInputStream(source));
                    fout = new BufferedOutputStream(new FileOutputStream(target));

                    // 读取文件, 写入文件
                    byte[] bytes = new byte[1024];
                    int result;
                    while ((result = fin.read(bytes)) != -1) {
                        fout.write(bytes, 0, result);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    // 关闭流
                    close(fin);
                    close(fout);
                }
            }
        };

        // 通过Buffer的Channel来完成拷贝
        FileCopy nioBufferCopy = new FileCopy() {
            @Override
            public void copyFile(File source, File target) {
                FileChannel fin = null;
                FileChannel fout = null;

                try {
                    fin = new FileInputStream(source).getChannel();
                    fout = new FileOutputStream(target).getChannel();

                    // 创建 nio buffer, 设置大小为 1024
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    // 从通道读取数据, 且写入buffer
                    while ((fin.read(buffer) != -1)) {
                        // 读模式转换为写模式
                        buffer.flip();
                        // 判断缓存中是否有剩余数据
                        // 因为write()并不能保证完成全部数据写入通道
                        while (buffer.hasRemaining()) {
                            // 往通道写操作, 需要从buffer读数据
                            // 这里使用fin, fout都可以, 因为 channel是双向的
                            fout.write(buffer);
                        }
                        // 写模式转换为读模式
                        buffer.clear();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    // 关闭channel
                    close(fin);
                    close(fout);
                }
            }
        };

        // channel之间直接进行拷贝
        FileCopy nioTransferCopy = new FileCopy() {
            @Override
            public void copyFile(File source, File target) {
                FileChannel fin = null;
                FileChannel fout = null;

                try {
                    fin = new FileInputStream(source).getChannel();
                    fout = new FileOutputStream(target).getChannel();

                    // transferTo() 方法, 通道之间的文件复制
                    // 参数1, 起始索引
                    // 参数2, 结束索引, 这里调用 fin.size()可以获取文件大小
                    // 参数3, 目标文件
                    // 返回值, 文件每次传输数量
                    // 缺点 : 并不能保证完成传输全部数据
                    long size = fin.size();
                    long transfer = 0L;
                    while (transfer != size) {
                        transfer = fin.transferTo(0, size, fout);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    // 关闭channel
                    close(fin);
                    close(fout);
                }
            }
        };

        File resource = new File("phoenix.mp3");
        File target = new File("phoenix.mp4");

        log.info("======开始复制测试=======");

        // 所需时间最长
        /*Long noBufferStreamCopyTime = copyTest(noBufferStreamCopy, resource, target);
        log.info(String.valueOf(noBufferStreamCopyTime));*/

        // 5
        Long bufferStreamCopyTime = copyTest(bufferStreamCopy, resource, target);
        log.info(String.valueOf(bufferStreamCopyTime));

        // 25
        Long nioBufferCopyTime = copyTest(nioBufferCopy, resource, target);
        log.info(String.valueOf(nioBufferCopyTime));

        // 5
        Long nioTransferCopyTime = copyTest(nioTransferCopy, resource, target);
        log.info(String.valueOf(nioTransferCopyTime));
    }
}
